package com.zte.claa.inficombo.csa.app.service.dataredeal.impl;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.mina.util.ConcurrentHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.zte.claa.inficombo.csa.app.config.Ga70DataRecheckProps;
import com.zte.claa.inficombo.csa.app.model.dev.DevTypeModel;
import com.zte.claa.inficombo.csa.app.model.redis.DevMeteData;
import com.zte.claa.inficombo.csa.app.service.csa.impl.BaseCacheManager;
import com.zte.claa.inficombo.csa.app.service.dataredeal.IMetedataRedealer;
import com.zte.claa.inficombo.csa.app.service.dataredeal.MetedataRedealService;
import com.zte.claa.inficombo.csa.app.service.persistent.CSADataPersistService;
import com.zte.claa.inficombo.csa.app.service.redis.CSAMessagePublisher;
import com.zte.claa.inficombo.csa.app.util.JsonUtils;

/**
 * GA70改造的门磁设备类型，metedata数据二次处理服务。
 * 主要逻辑：
	1、GA70探测到MEMS震动消息及计步消息，上报消息到combo；
	2、combo侧根据MEMS和计步信息，判断门打开（指标状态为开门），同时开启关门判断定时器；
	3、如果定时器到期后，没有产生新的MEMS消息，则补充产生关门数据并上报，否则顺延定时器；
	4、GA70按新的普通lora终端设备类型来定义（克拉门磁V2）；
 * 
 * @author 0283000115
 *
 */
@Service
public class Ga70DoorDataRecheckService implements IMetedataRedealer, InitializingBean, DisposableBean {
	// 日志
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private BaseCacheManager cacheManager;
	
	@Autowired
	private CSADataPersistService persisService;
	
	@Autowired
	private MetedataRedealService redealEntranceSvr;
	
	@Autowired
	private Ga70DataRecheckProps recheckProps;
	
	// 消息发布器
	private CSAMessagePublisher msgPublisher = null;
	
	// 待处理的deveui集合
	private Set<String> deveuiSet = null;
	
	// closedoor-补发检测定时器
	private ScheduledExecutorService doorStateCheckScheduler = null;
	
	@Override
	public void afterPropertiesSet() throws Exception {		
		// 向二次处理入口服务注册.
		String devTypeId = this.recheckProps.getDevtype();
		if(devTypeId == null || devTypeId.trim().equals("")){
			logger.warn("[!@@!]Ga70DoorDataRecheckService configure devtype is null!");
			return;
		}
		this.deveuiSet = new ConcurrentHashSet<String>();	
		// 向入口服务注册.
		this.redealEntranceSvr.registMetedataRedealer(devTypeId.trim(), this); 			
		// 启动close补发检查定时器. 
		this.doorStateCheckScheduler = Executors.newScheduledThreadPool(1);
		//分包数据过期清理.
		Runnable task = new Runnable(){
			@Override
			public void run() {
				checkDataTimeout();
			}
		};
		// 按固定间隔调度.
		this.doorStateCheckScheduler.scheduleAtFixedRate(task, 10, this.recheckProps.getCheckperiod(), TimeUnit.SECONDS);
		logger.info("[**]Ga70 door timer check task is submitted and will be scheduled in 10 seconds later!");	
	}

	@Override
	public void destroy() throws Exception {
		if(this.doorStateCheckScheduler != null){
			this.doorStateCheckScheduler.shutdown();
			this.doorStateCheckScheduler = null;
		} 
		this.msgPublisher = null;
	}

	@Override 
	public void setCSAMessagePublisher(CSAMessagePublisher publisher) {
		this.msgPublisher = publisher;
	}

	/**
	 * 基本逻辑：
	 * 1）如果当前没有缓存数据，将数据存入缓存，同时做正常持久化保存和消息发布；
	 * 2）如果当前有缓存（说明最近30秒内有数据上报），则根据twicesamevalueisclose的配置判断： 
	 *    （2.1）true则转换成door-close消息持久化、消息发布、删除缓存；
	 *    （2.2）false的话，直接更新缓存，同时做正常持久化保存和消息发布；
	 */
	@Override
	public void submitMetedata(DevMeteData data) {
		// 检查是否需要合并；检查是否需要发布；
		try {
			String devEui = data.getDeveui();
			DevMeteData curData = this.cacheManager.getCombineData(devEui);
			if(curData == null){
				this.deveuiSet.add(devEui);
				this.cacheManager.putCombineData(data);
				this.processMeteData(data);
				return;
			}
			if(this.recheckProps.isTwicesamevalueisclose() == false){
				this.deveuiSet.add(devEui);
				this.cacheManager.putCombineData(data);
				this.processMeteData(data);
				return;
			}
			// 比较关键值.
			boolean isTwiceSameVal = false;
			String fieldName = this.recheckProps.getFieldname();
			Map<String, Object> curMap = curData.getMdata();
			Map<String, Object> newMap = data.getMdata();
			if(curMap != null && newMap != null){
				Object curVal = curMap.get(fieldName);
				Object newVal = newMap.get(fieldName);
				if(curVal != null && newVal != null && newVal.toString().equals(curVal.toString())){
					newMap.put(fieldName, this.recheckProps.getFieldresetvalue());
					isTwiceSameVal = true;
				}
			}
			if(isTwiceSameVal){
				this.deveuiSet.remove(devEui);
				this.cacheManager.removeCombineData(devEui);				
			} else {
				this.deveuiSet.add(devEui);
				this.cacheManager.putCombineData(data);
			}
			this.processMeteData(data);	
		}catch(Exception e){
			logger.error("submitMetedata failed! "+data.getDeveui()+", err="+e.getMessage(), e);
		}
	}
	
	// 提交一批待合并数据（前提是需要合并）
	public void submitMetedatas(List<DevMeteData> dataList) {
		if(dataList == null || dataList.size() == 0){
			return;
		}
		for(DevMeteData data : dataList){
			this.submitMetedata(data);
		}
	}
	
	// 判断数据合并是否已经超时.
	private boolean isTimeout(long curTs, Timestamp dataOrgRptTime){
		return (curTs - dataOrgRptTime.getTime()) >= (1000L * this.recheckProps.getTimeoutseconds()); //Unintentional integer overflow
	}
	
	/**
	 * 定时检查数据（是否超时）
	 */
	private void checkDataTimeout(){
		// 复制一份deveui列表，避免和删除操作冲突.
		List<String> tmpList = new ArrayList<String>();	
		tmpList.addAll(this.deveuiSet);
		String fieldName = this.recheckProps.getFieldname();
		String resetValue = this.recheckProps.getFieldresetvalue();
		long curTs = System.currentTimeMillis();
		for(String devEui : tmpList){
			try {
				DevMeteData data = this.cacheManager.getCombineData(devEui);
				if (data == null) {
					continue;
				}
				if (this.isTimeout(curTs, data.getRpttime())) {
					this.cacheManager.removeCombineData(devEui);
					this.deveuiSet.remove(devEui);
					Map<String, Object> curMap = data.getMdata();
					if(curMap != null){
						curMap.put(fieldName, resetValue);
					}					
					// 更新上报时间.
					data.setRpttime(new Timestamp(System.currentTimeMillis()));
					this.processMeteData(data); 
				}
			}catch(Exception e){
				logger.error("checkDataTimeout failed: deveui="+devEui+", err="+e.getMessage());
			}
		}	
		tmpList.clear();
		tmpList = null;
	}
	
	// 持久化并发布数据.
	private void processMeteData(DevMeteData data) throws Exception {
		String devTypeId = data.getDevtype();
		DevTypeModel dtm = this.cacheManager.getDevTypeModel(devTypeId);
		// 1.持久化数据.
		this.persisService.saveDevMeteData(data, dtm.getBase().getTbname());
		// 2.构造广播数据，进行redis发布...
		String pubMsg = JsonUtils.toJson(data);
		if(this.msgPublisher != null){
			this.msgPublisher.publishMessage(pubMsg);
			logger.info("[*^_^*]Recheck and publish metedata message ok: "+pubMsg);
		} else {
			logger.info("[*@@*]Recheck metedata message ok but publisher is null: "+pubMsg);
		}
	}

}
